/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.raflik.queryxml.ui;

import java.awt.Color;
import java.awt.event.ItemEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.openide.cookies.EditCookie;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.OutlineView;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node.Property;
import org.openide.nodes.Node.PropertySet;
import org.openide.nodes.Sheet;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import pl.raflik.queryxml.langservice.QueryLanguage;
import pl.raflik.queryxml.serializer.QueryResultSerializer;
import pl.raflik.queryxml.ui.cellrenderer.*;
import pl.raflik.queryxml.ui.model.ResultNode;
import pl.raflik.queryxml.ui.tasks.*;
import pl.raflik.queryxml.ui.tasks.io.*;

/**
 * Top component which displays something.
 */
final class QueryTopComponent extends TopComponent implements ExplorerManager.Provider {

    // Colors
    private static final Color NORMAL_TEXT_AREA_FOREGROUND_COLOR = UIManager.getColor("TextArea.foreground");
    private static final Color ERROR_TEXT_AREA_FOREGROUND_COLOR = NORMAL_TEXT_AREA_FOREGROUND_COLOR.equals(Color.RED) ? Color.MAGENTA : Color.RED;
    private static final Color NORMAL_LABEL_FOREGROUND_COLOR = UIManager.getColor("Label.foreground");
    private static final Color ERROR_LABEL_FOREGROUND_COLOR = NORMAL_LABEL_FOREGROUND_COLOR.equals(Color.RED) ? Color.MAGENTA : Color.RED;
    // Generated
    private static final String PREFERRED_ID = "QueryTopComponent";
    private static final String defaultMimeType = "text/plain";
    private static QueryTopComponent instance;
    /** path to the icon used by the component and its open action */
//    static final String ICON_PATH = "SET/PATH/TO/ICON/HERE";

    // instance variables
    private final ExplorerManager explMgr = new ExplorerManager();
    private Lookup.Result<QueryLanguage> languageLookupResult = null;
    private Lookup.Result<QueryResultSerializer> serializerLookupResult = null;
    private IHistoryListModel history = new QueryHistory();
    private final RequestProcessor threadPool = new RequestProcessor("QUERY_PROCESSING_THREAD", 1);
    //private final RowModel resultRowModel = new ResultRowModel();
    private AbstractNode justRoot = new ResultNode();

    private String resultToMessage(QueryResultCode result, Object... args) {
        String msg = null;
        switch (result) {
            case QUERY_OK:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.queryCompletedIn");
                Long time = args.length == 0 ? null : ((Long) args[0]);
                if (time != null) {
                    msg = String.format(msg, time.longValue());
                }
                break;
            case RESULT_EMPTY:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.resultEmpty");
                break;
            case NO_QUERY_LANG:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.noQueryLanguage");
                break;
            case QUERY_EMPTY:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.queryEmpty");
                break;
            case QUERY_NOT_VALID:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.queryError");
                break;
            case NO_INPUT_FILE:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.noSource");
                break;
            case INPUT_FILE_NOT_FOUND:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.fileNotFound");
                break;
            case INPUT_READ_FAILED:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.inputReadFailed");
                break;
            case TEMP_FILE_CREATE_FAILED:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.tempFileCreateFailed");
                break;
            case TEMP_FILE_WRITE_FAILED:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.tempFileWriteFailed");
                break;
            case SERIALIZATION_FAILED:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.xmlSerializationFailed");
                break;
            default:
                msg = NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.queryFailed");
                break;
        }
        return msg;
    }
    private Map<String, org.w3c.dom.Document> cache = Collections.synchronizedMap(new WeakHashMap<String, org.w3c.dom.Document>());

    //new tasks
    private abstract class CommonQueryUITransfer {

        public CommonQueryInput readUI() {
            final String query = queryEP.getText();
            final QueryLanguage ql = ((QueryLanguage) languageCB.getSelectedItem());
            final FileObject sourceFO = (FileObject) sourceCB.getSelectedItem();
            return new CommonQueryInput(ql, query, sourceFO);
        }
    };

    private class PreviewQueryUITransfer extends CommonQueryUITransfer implements UITransfer<PreviewQueryInput, PreviewQueryOutput> {

        //private OutlineModel emptyOutlineModel = DefaultOutlineModel.createOutlineModel(new ResultTreeModel(null), resultRowModel, false, ResultRowModel.Columns.values()[0].toText());
        @Override
        public PreviewQueryInput readUI() {
            final CommonQueryInput i = super.readUI();
            return new PreviewQueryInput(i.getQueryLanguage(), i.getQuery(), i.getFileObject());
        }

        public void writeUI(PreviewQueryOutput out) {
            final QueryResultCode result = out.getResultCode();
            final long processingTime = out.getProcessingTime();
            final List<?> output = out.getQueryResult();

            final String status = resultToMessage(result, processingTime);
            statusL.setText(status);

            //OutlineModel outlineModel = emptyOutlineModel;
            AbstractNode rootNode = justRoot;
            if (!result.isError()) {
                if (output != null) {
                    final List<Object> extractedOutput = new ArrayList<Object>();
                    for (Object outputEl : output) {
                        if (outputEl != null) {
                            extractedOutput.add(outputEl);
                        }
                    }
                    if (extractedOutput.size() > 0) {
                        //final String firstColHeader = ResultRowModel.Columns.values()[0].toText();
                        //outlineModel = DefaultOutlineModel.createOutlineModel(new ResultTreeModel(extractedOutput), resultRowModel, false, firstColHeader);
                        rootNode = new ResultNode(extractedOutput);//new ResultNode(extractedOutput);
                    }
                }

                statusL.setForeground(NORMAL_LABEL_FOREGROUND_COLOR);
            } else {
                statusL.setForeground(ERROR_LABEL_FOREGROUND_COLOR);
            }
            //resultO.setModel(outlineModel);
            explMgr.setRootContext(rootNode);

            if (result != QueryResultCode.QUERY_NOT_VALID) {
                queryEP.setForeground(NORMAL_TEXT_AREA_FOREGROUND_COLOR);
            } else {
                queryEP.setForeground(ERROR_TEXT_AREA_FOREGROUND_COLOR);
            }

        }
    }
    private final Runnable previewQueryThread = new PreviewQueryThread(this.new PreviewQueryUITransfer(), cache);
    private static final int QUERY_CHANGE_HANDLER_DELAY = 250;
    private final RequestProcessor.Task previewQueryTask = threadPool.create(previewQueryThread);

    private class RunQueryUITransfer extends CommonQueryUITransfer implements UITransfer<RunQueryInput, RunQueryOutput> {

        @Override
        public RunQueryInput readUI() {
            final CommonQueryInput i = super.readUI();
            final QueryResultSerializer serializer = (QueryResultSerializer) serializerCB.getSelectedItem();
            return new RunQueryInput(i.getQueryLanguage(), i.getQuery(), i.getFileObject(), serializer);
        }

        public void writeUI(RunQueryOutput out) {
            final QueryResultCode result = out.getResultCode();
            final FileObject fo = out.getFileObject();
            final String query = out.getQuery();

            history.store(query);

            if (result.isError()) {
                final String msg = resultToMessage(result);
                if (msg != null) {
                    MessageDisplayer.showError(msg);
                }
            } else if (result == QueryResultCode.RESULT_EMPTY) {
                final String msg = resultToMessage(result);
                MessageDisplayer.showWarning(msg);
            } else {
                openResultInEditor(fo, true, true);
            }
        }
    }
    private final Runnable runQueryHandler = new RunQueryThread(this.new RunQueryUITransfer(), cache);
    private static final int RUN_QUERY_HANDLER_DELAY = 100;
    private RequestProcessor.Task runQueryHandlerTask = threadPool.create(runQueryHandler);

    private QueryTopComponent() {
        initComponents();

        setName(NbBundle.getMessage(QueryTopComponent.class, "CTL_QueryTopComponent"));
        setToolTipText(NbBundle.getMessage(QueryTopComponent.class, "HINT_QueryTopComponent"));
//        setIcon(Utilities.loadImage(ICON_PATH, true));
        putClientProperty(
                "netbeans.winsys.tc.keep_preferred_size_when_slided_in", Boolean.TRUE);

        PropertySet[] propSets = justRoot.getPropertySets();
        Property[] props = null;
        for (int i = 0; i < propSets.length; i++) {
            PropertySet propertySet = propSets[i];
            if (propertySet.getName().equals(Sheet.PROPERTIES)) {
                props = propertySet.getProperties();
                break;
            }
        }
        resultOV.getOutline().setRootVisible(false);
        resultOV.setProperties(props);

//        resultOV.setRenderDataProvider(
//                new ResultRenderDataProvider());
    }

    private void doPreviewQuery() {
        previewQueryTask.schedule(QUERY_CHANGE_HANDLER_DELAY);
    }

    private void doRunQuery() {
        runQueryHandlerTask.schedule(RUN_QUERY_HANDLER_DELAY);
    }

    private boolean openInEditor(DataObject dObj, boolean makeActive) throws IOException {
        boolean ret = false;
        EditCookie ck = dObj.getCookie(EditCookie.class);

        if (ck != null) {
            ck.edit();
            ret = true;
        } else {
            OpenCookie oc = dObj.getCookie(OpenCookie.class);
            if (oc != null) {
                oc.open();
                ret = true;
            }
        }
        if (ret && makeActive) {
            EditorCookie ed = dObj.getCookie(EditorCookie.class);
            if (ed != null) {
                ed.openDocument();
                JEditorPane pane = ed.getOpenedPanes()[0];
                TopComponent tc = (TopComponent) SwingUtilities.getAncestorOfClass(TopComponent.class,
                        pane);
                if (tc != null) { // It won't be
                    tc.requestActive();
                }
            }
        }
        return ret;
    }

    private void openResultInEditor(FileObject destFO, boolean makeActive, boolean keepSelectedSource) {
        try {
            DataObject destDO = DataObject.find(destFO);
            try {
                Object inputItem = sourceCB.getSelectedItem();
                openInEditor(destDO, makeActive);
                if (keepSelectedSource) {
                    sourceCB.setSelectedItem(inputItem);
                }

            } catch (IOException e) {
                Exceptions.printStackTrace(e);
            }

        } catch (DataObjectNotFoundException e) {
            Exceptions.printStackTrace(e);
        }

    }

    private synchronized void updateLanguageCBModel() {
        updateCBModel(languageCB, QueryLanguage.class, new Comparator<QueryLanguage>() {

            public int compare(QueryLanguage o1, QueryLanguage o2) {
                return o1.supportedLanguage().compareTo(o2.supportedLanguage());
            }
        });
    }

    private synchronized void updateSerializerCBModel() {
        updateCBModel(serializerCB, QueryResultSerializer.class, new Comparator<QueryResultSerializer>() {

            public int compare(QueryResultSerializer o1, QueryResultSerializer o2) {
                return o1.supportedOutputType().compareTo(o2.supportedOutputType());
            }
        });
    }

    private <T> void updateCBModel(JComboBox cb, Class<T> clazz, Comparator<T> comp) {

        List<T> ts = new ArrayList<T>(Lookup.getDefault().lookupAll(clazz));
        Collections.sort(ts, comp);

        cb.removeAllItems();
        for (T t : ts) {
            cb.addItem(t);
        }
        if (cb.getItemCount() == 0) {
            cb.setSelectedItem(null);
        }
    }

    private synchronized void updateSourceCBModel() {
        final Registry reg = TopComponent.getRegistry();
        final Set<TopComponent> openedTCs = reg.getOpened();
        final List<FileObject> xmlFObjs = new ArrayList<FileObject>();
        for (TopComponent openedTC : openedTCs) {
            FileObject fObj = openedTC.getLookup().lookup(FileObject.class);

            if (fObj != null && fObj.isValid() && fObj.getMIMEType().toLowerCase().endsWith("xml")) {
                xmlFObjs.add(fObj);
            }
        }
        Collections.sort(xmlFObjs, new Comparator<FileObject>() {

            public int compare(FileObject o1, FileObject o2) {
                return o1.getNameExt().compareTo(o2.getNameExt());
            }
        });


//        List<Object> toRemove = new ArrayList<Object>();
//        for (int i = 0; i < sourceCB.getItemCount(); i++) {
//            toRemove.add(sourceCB.getItemAt(i));
//        }
//        for (Object obj : toRemove) {
//            ((MutableComboBoxModel)sourceCB.getModel()).removeElement(obj);
//        }
        //sourceCB.configureEditor(sourceCB.getEditor(), "");
        sourceCB.removeAllItems();
        for (FileObject fo : xmlFObjs) {
            sourceCB.addItem(fo);
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        buttonGroup1 = new javax.swing.ButtonGroup();
        jPanel1 = new javax.swing.JPanel();
        sourceCB = new javax.swing.JComboBox();
        sourceL = new javax.swing.JLabel();
        historyL = new javax.swing.JLabel();
        historyCB = new javax.swing.JComboBox();
        deleteB = new javax.swing.JButton();
        queryL = new javax.swing.JLabel();
        clearHistoryB = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        queryEP = new javax.swing.JEditorPane();
        jPanel2 = new javax.swing.JPanel();
        runB = new javax.swing.JButton();
        clearB = new javax.swing.JButton();
        jPanel4 = new javax.swing.JPanel();
        jPanel5 = new javax.swing.JPanel();
        languageL = new javax.swing.JLabel();
        languageCB = new javax.swing.JComboBox();
        serializationL = new javax.swing.JLabel();
        serializerCB = new javax.swing.JComboBox();
        jPanel6 = new javax.swing.JPanel();
        jPanel7 = new javax.swing.JPanel();
        jSeparator1 = new javax.swing.JSeparator();
        statusL = new javax.swing.JLabel();
        resultOV = new OutlineView(NbBundle.getMessage(ResultNode.class, "ResultNode.Columns.Type"));

        setPreferredSize(new java.awt.Dimension(450, 500));
        setLayout(new java.awt.BorderLayout());

        jPanel1.setPreferredSize(new java.awt.Dimension(400, 160));

        sourceCB.setRenderer(new XMLEditorListCellRenderer());
        sourceCB.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                sourceCBItemStateChanged(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(sourceL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.sourceL.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(historyL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.historyL.text")); // NOI18N

        historyCB.setModel(history);
        historyCB.setRenderer(new QueryListCellRenderer());
        historyCB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                historyCBActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(deleteB, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.deleteB.text")); // NOI18N
        deleteB.setPreferredSize(new java.awt.Dimension(75, 23));
        deleteB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                deleteBActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(queryL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.queryL.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(clearHistoryB, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.clearHistoryB.text")); // NOI18N
        clearHistoryB.setMaximumSize(new java.awt.Dimension(65, 23));
        clearHistoryB.setMinimumSize(new java.awt.Dimension(65, 23));
        clearHistoryB.setPreferredSize(new java.awt.Dimension(75, 23));
        clearHistoryB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                clearHistoryBActionPerformed(evt);
            }
        });

        jScrollPane1.setViewportView(queryEP);

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
                        .addComponent(queryL)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 392, Short.MAX_VALUE))
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
                        .addComponent(sourceL)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(sourceCB, 0, 384, Short.MAX_VALUE))
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
                        .addComponent(historyL)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(historyCB, 0, 181, Short.MAX_VALUE)
                        .addGap(18, 18, 18)
                        .addComponent(deleteB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(clearHistoryB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(sourceCB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(sourceL))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(historyL)
                    .addComponent(historyCB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(clearHistoryB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(deleteB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(jPanel1Layout.createSequentialGroup()
                        .addComponent(queryL)
                        .addGap(68, 68, 68))
                    .addComponent(jScrollPane1)))
        );

        add(jPanel1, java.awt.BorderLayout.PAGE_START);

        jPanel2.setPreferredSize(new java.awt.Dimension(400, 300));

        org.openide.awt.Mnemonics.setLocalizedText(runB, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.runB.text")); // NOI18N
        runB.setPreferredSize(new java.awt.Dimension(75, 23));
        runB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                runBActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(clearB, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.clearB.text")); // NOI18N
        clearB.setPreferredSize(new java.awt.Dimension(75, 23));
        clearB.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                clearBActionPerformed(evt);
            }
        });

        jPanel4.setLayout(new javax.swing.BoxLayout(jPanel4, javax.swing.BoxLayout.PAGE_AXIS));

        jPanel5.setPreferredSize(new java.awt.Dimension(200, 30));
        jPanel5.setRequestFocusEnabled(false);

        org.openide.awt.Mnemonics.setLocalizedText(languageL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.languageL.text")); // NOI18N

        languageCB.setRenderer(new LanguageListCellRenderer());
        languageCB.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                languageCBItemStateChanged(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(serializationL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.serializationL.text")); // NOI18N

        serializerCB.setRenderer(new SerializerListCellRenderer());

        javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
        jPanel5.setLayout(jPanel5Layout);
        jPanel5Layout.setHorizontalGroup(
            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel5Layout.createSequentialGroup()
                .addComponent(languageL)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(languageCB, 0, 143, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(serializationL)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(serializerCB, 0, 159, Short.MAX_VALUE))
        );
        jPanel5Layout.setVerticalGroup(
            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel5Layout.createSequentialGroup()
                .addGap(4, 4, 4)
                .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(languageCB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(languageL)
                    .addComponent(serializerCB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(serializationL))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        jPanel4.add(jPanel5);

        jPanel6.setPreferredSize(new java.awt.Dimension(380, 200));
        jPanel6.setLayout(new java.awt.BorderLayout());

        jPanel7.setPreferredSize(new java.awt.Dimension(426, 20));
        jPanel7.setLayout(new java.awt.BorderLayout());

        jSeparator1.setPreferredSize(new java.awt.Dimension(50, 5));
        jPanel7.add(jSeparator1, java.awt.BorderLayout.NORTH);

        org.openide.awt.Mnemonics.setLocalizedText(statusL, org.openide.util.NbBundle.getMessage(QueryTopComponent.class, "QueryTopComponent.statusL.text")); // NOI18N
        jPanel7.add(statusL, java.awt.BorderLayout.CENTER);

        jPanel6.add(jPanel7, java.awt.BorderLayout.PAGE_END);
        jPanel6.add(resultOV, java.awt.BorderLayout.CENTER);

        jPanel4.add(jPanel6);

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(runB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addComponent(clearB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel2Layout.createSequentialGroup()
                        .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, 430, Short.MAX_VALUE)
                        .addContainerGap())))
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(runB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(clearB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE))
        );

        add(jPanel2, java.awt.BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents

    private void deleteBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteBActionPerformed
        Object item = history.getSelectedItem();
        history.removeElement(item);
    }//GEN-LAST:event_deleteBActionPerformed

    private void runBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runBActionPerformed
        doRunQuery();
    }//GEN-LAST:event_runBActionPerformed

    private enum State {

        NOTHING,
        DESELECTED,
    }
    private State state = State.NOTHING;

    private void languageCBItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_languageCBItemStateChanged
        boolean performUpdate = false;
        switch (evt.getStateChange()) {
            case ItemEvent.DESELECTED:
                switch (state) {
                    case NOTHING:
                        state = State.DESELECTED;
                        break;
                    case DESELECTED:
                        break;
                }
                break;
            case ItemEvent.SELECTED:
                state = State.NOTHING;
                performUpdate = true;
                break;
        }
        if (!performUpdate) {
            return;
        }

        String mimeType = defaultMimeType;
        QueryLanguage queryLanguage = (QueryLanguage) languageCB.getSelectedItem();
        if (queryLanguage != null) {
            mimeType = queryLanguage.mimeType();
        }
        if (mimeType.equals(queryEP.getContentType())) {
            return;
        }

        Document oldDoc = queryEP.getDocument();
        oldDoc.removeDocumentListener(queryTextListener);
        int length = oldDoc.getLength();
        String text = "";
        try {
            text = oldDoc.getText(0, length);
        } catch (BadLocationException e) {
        }
        queryEP.setContentType(mimeType);
        Document newDoc = queryEP.getDocument();
        try {
            newDoc.insertString(0, text, null);
        } catch (BadLocationException e) {
        }
        newDoc.addDocumentListener(queryTextListener);
        doPreviewQuery();
    }//GEN-LAST:event_languageCBItemStateChanged

    private void clearHistoryBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearHistoryBActionPerformed
        history.clear();
    }//GEN-LAST:event_clearHistoryBActionPerformed

    private void historyCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_historyCBActionPerformed
        JComboBox cb = (JComboBox) evt.getSource();
        String query = (String) cb.getSelectedItem();
        queryEP.setText(query);
    }//GEN-LAST:event_historyCBActionPerformed

    private void clearBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearBActionPerformed
        queryEP.setText("");
    }//GEN-LAST:event_clearBActionPerformed

    private void sourceCBItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_sourceCBItemStateChanged
        doPreviewQuery();
    }//GEN-LAST:event_sourceCBItemStateChanged

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.ButtonGroup buttonGroup1;
    private javax.swing.JButton clearB;
    private javax.swing.JButton clearHistoryB;
    private javax.swing.JButton deleteB;
    private javax.swing.JComboBox historyCB;
    private javax.swing.JLabel historyL;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JPanel jPanel5;
    private javax.swing.JPanel jPanel6;
    private javax.swing.JPanel jPanel7;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JComboBox languageCB;
    private javax.swing.JLabel languageL;
    private javax.swing.JEditorPane queryEP;
    private javax.swing.JLabel queryL;
    private org.openide.explorer.view.OutlineView resultOV;
    private javax.swing.JButton runB;
    private javax.swing.JLabel serializationL;
    private javax.swing.JComboBox serializerCB;
    private javax.swing.JComboBox sourceCB;
    private javax.swing.JLabel sourceL;
    private javax.swing.JLabel statusL;
    // End of variables declaration//GEN-END:variables

    /**
     * Gets default instance. Do not use directly: reserved for *.settings files only,
     * i.e. deserialization routines; otherwise you could get a non-deserialized instance.
     * To obtain the singleton instance, use {@link #findInstance}.
     */
    public static synchronized QueryTopComponent getDefault() {
        if (instance == null) {
            instance = new QueryTopComponent();
        }

        return instance;
    }

    /**
     * Obtain the QueryTopComponent instance. Never call {@link #getDefault} directly!
     */
    public static synchronized QueryTopComponent findInstance() {
        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
        if (win == null) {
            Logger.getLogger(QueryTopComponent.class.getName()).warning(
                    "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
            return getDefault();
        }
        if (win instanceof QueryTopComponent) {
            return (QueryTopComponent) win;
        }

        Logger.getLogger(QueryTopComponent.class.getName()).warning(
                "There seem to be multiple components with the '" + PREFERRED_ID +
                "' ID. That is a potential source of errors and unexpected behavior.");
        return getDefault();
    }

    @Override
    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_ALWAYS;
    }
    private final DocumentListener queryTextListener = new DocumentListener() {

        private void textChanged() {
            doPreviewQuery();
        }

        public void insertUpdate(DocumentEvent e) {
            textChanged();
        }

        public void removeUpdate(DocumentEvent e) {
            textChanged();
        }

        public void changedUpdate(DocumentEvent e) {
            //nothing
            }
    };
    private final PropertyChangeListener openedPropertyListener = new PropertyChangeListener() {

        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals(TopComponent.Registry.PROP_OPENED)) {
                updateSourceCBModel();
            }

        }
    };
    private final LookupListener languageLookupListener = new LookupListener() {

        public void resultChanged(LookupEvent ev) {
            if (ev.getSource().equals(languageLookupResult)) {
                updateLanguageCBModel();
            }

        }
    };
    private final LookupListener serializerLookupListener = new LookupListener() {

        public void resultChanged(LookupEvent ev) {
            if (ev.getSource().equals(serializerLookupResult)) {
                updateSerializerCBModel();
            }

        }
    };

    @Override
    public void componentOpened() {
        queryEP.getDocument().addDocumentListener(queryTextListener);

        Lookup.Template<QueryLanguage> queryLanguageTemplate = new Lookup.Template<QueryLanguage>(QueryLanguage.class);
        languageLookupResult = Lookup.getDefault().lookup(queryLanguageTemplate);

        languageLookupResult.addLookupListener(languageLookupListener);

        updateLanguageCBModel();

        Lookup.Template<QueryResultSerializer> serializerTemplate = new Lookup.Template<QueryResultSerializer>(QueryResultSerializer.class);
        serializerLookupResult = Lookup.getDefault().lookup(serializerTemplate);
        serializerLookupResult.addLookupListener(serializerLookupListener);
        updateSerializerCBModel();

        TopComponent.getRegistry().addPropertyChangeListener(openedPropertyListener);
        updateSourceCBModel();
    }

    @Override
    public void componentClosed() {
        TopComponent.getRegistry().removePropertyChangeListener(openedPropertyListener);

        serializerLookupResult.removeLookupListener(serializerLookupListener);
        serializerLookupResult = null;

        languageLookupResult.removeLookupListener(languageLookupListener);
        languageLookupResult = null;

        queryEP.getDocument().removeDocumentListener(queryTextListener);
    }

    /** replaces this in object stream */
    @Override
    public Object writeReplace() {
        return new ResolvableHelper(history);
    }

    @Override
    protected String preferredID() {
        return PREFERRED_ID;
    }

    public ExplorerManager getExplorerManager() {
        return this.explMgr;
    }

    final static class ResolvableHelper implements Serializable {

        private static final long serialVersionUID = 1L;
        private IHistoryListModel history;

        public ResolvableHelper(IHistoryListModel history) {
            this.history = history;
        }

        public Object readResolve() {
            QueryTopComponent queryTopComponent = QueryTopComponent.getDefault();
            if (history != null) {
                queryTopComponent.history = history;
                queryTopComponent.historyCB.setModel(history);
            }
            return queryTopComponent;
        }
    }
}
